package semant.second_pass;

import util.List;
import symbol.Symbol;
import symbol.ClassInfo;
import semant.Env;
import syntaxtree.Program;
import syntaxtree.ClassDecl;
import syntaxtree.ClassDeclExtends;
import syntaxtree.VisitorAdapter;

final class InheritanceBuilder extends VisitorAdapter {

    private Env env;
    private List<ClassDecl> classes;

    private InheritanceBuilder(Env e, List<ClassDecl> c){
	super();
	env = e;
	classes = c;
    }

    static void secondPass(Env e, Program p){
	if(p.classList != null){
	    InheritanceBuilder h = new InheritanceBuilder(e, p.classList.clone());
	    for(List<ClassDecl> cd = p.classList; cd != null; cd = cd.tail)
		if(cd.head instanceof ClassDeclExtends)
		    ((ClassDeclExtends)cd.head).accept(h);
	}
    }

    public void visit(ClassDeclExtends node){
	Symbol cname = Symbol.symbol(node.name.s);
	Symbol bname = Symbol.symbol(node.superClass.s);

	ClassInfo cinfo = env.classes.get(cname);
	ClassInfo binfo = env.classes.get(bname);

	// testa se classe extendida existe e se ja herdou de outra classe
	if(cinfo == null || cinfo.base != null)
	    return;

	// testa se classe base existe
	if(binfo == null){
	    env.err.Error(node, new Object[]{"Classe '" + cname + "' deriva de classe nao declarada '" + bname + "'"});
	    return;
	}else{
	    // se a classe base herda de uma terceira, devemos garantir
	    // que o procedimento serah aplicado a ela primeiro
	    if(cname != bname){
		for(List<ClassDecl> cd = classes, l=null; cd != null; l = cd, cd = cd.tail){
		    Symbol n = Symbol.symbol(cd.head.name.s);
		    if(n == bname){
			// classe base nao devera ser encontrada novamente
			// remove classe base da lista
			if(l != null)
			    l.tail = cd.tail;
			else
			    classes = cd.tail;
			// processa heranca da classe base
			if(cd.head instanceof ClassDeclExtends)
			    visit((ClassDeclExtends)cd.head);
			break;
		    }
		}
	    }

	    // heranca circular?
	    if(!TypeHandler.canInheritFrom(binfo, cinfo))
		env.err.Error(node, new Object[]{"Heranca circular detectada entre as classes '" + bname + "' e '" + cname + "'"});
	    else
		cinfo.setBase(binfo); // tudo ok!
	}
    }

}
